﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions;

namespace Avanzis.QueryBuilder.Fluent
{
    ///<summary>
    ///</summary>
    public class Select : ICloneable, ISelect
    {
        protected SelectBuilder builder;

        #region Constructor

        ///<summary>
        ///</summary>
        public Select()
        {
            this.builder = new SelectBuilder();
        }

        ///<summary>
        ///</summary>
        ///<param name = "builder"></param>
        public Select(SelectBuilder builder)
        {
            this.builder = (SelectBuilder)builder.Clone();
        }

        ///<summary>
        ///</summary>
        ///<param name = "select"></param>
        public Select(Select select)
        {
            this.builder = select.ToSelectBuilder();
        }

        ///<summary>
        ///</summary>
        ///<param name="commaSeparatedFields"></param>
        public Select(string commaSeparatedFields)
        {
            builder = new SelectBuilder();
            builder.Select = new SelectClause(commaSeparatedFields);
        }
       

        #endregion

        #region Field
        

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select Field(string expression)
        {
            builder.Fields.Add(expression);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "field"></param>
        ///<returns></returns>
        public Select Field(SqlField field)
        {
            builder.Fields.Add(field);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<typeparam name = "T"></typeparam>
        ///<returns></returns>
        public virtual Select Field<T>(Expression<Func<T, object>> expression)
        {
            var stringExpression = FluentHelper.ParseProperty(expression);

            return Field(stringExpression);
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<param name = "alias"></param>
        ///<returns></returns>
        public Select Field(string expression, string alias)
        {
            builder.Fields.Add(expression, alias);

            return this;
        }

        #endregion

        ///<summary>
        ///</summary>
        ///<param name = "tableName"></param>
        ///<returns></returns>
        public Select From(string tableName)
        {
            builder.From = new FromClause(tableName);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<typeparam name = "T"></typeparam>
        ///<returns></returns>
        public Select From<T>()
        {
            string tableName = FluentHelper.GetTableName<T>();
            return this.From(tableName);
        }

        ///<summary>
        ///</summary>
        ///<param name = "tableName"></param>
        ///<param name = "alias"></param>
        ///<returns></returns>
        public Select From(string tableName, string alias)
        {
            builder.From = new FromClause(tableName, alias);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<param name = "fieldValue"></param>
        ///<returns></returns>
        public Select Where(string fieldName, object fieldValue)
        {
            builder.Where.Filters.Add(fieldName, fieldValue);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select Where(string expression)
        {
            builder.Where.Filters.Add(expression);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "leftExpression"></param>
        ///<param name = "compareOperator"></param>
        ///<param name = "rightExpression"></param>
        ///<returns></returns>
        public Select Where(object leftExpression, SqlOperator compareOperator, object rightExpression)
        {
            builder.Where.Filters.Add(leftExpression, compareOperator, rightExpression);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<param name = "fieldValue"></param>
        ///<returns></returns>
        public Select Where<T>(Expression<Func<T, object>> fieldName, object fieldValue)
        {
            var stringExpression = FluentHelper.ParseProperty(fieldName);
            builder.Where.Filters.Add(stringExpression, fieldValue);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<param name = "compareOperator"></param>
        ///<param name = "fieldValue"></param>
        ///<returns></returns>
        public Select Where<T>(Expression<Func<T, object>> fieldName, SqlOperator compareOperator, object fieldValue)
        {
            var stringExpression = FluentHelper.ParseProperty(fieldName);
            builder.Where.Filters.Add(stringExpression, compareOperator, fieldValue);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "limit"></param>
        ///<returns></returns>
        public Select Take(int limit)
        {
            builder.Limit = limit;
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "offset"></param>
        ///<returns></returns>
        public Select Offset(int offset)
        {
            builder.Offset = offset;
            return this;
        }

        /// <summary>
        ///   Returns a <see cref = "T:System.String" /> that represents the SQL.
        /// </summary>
        /// <returns>
        ///   A <see cref = "T:System.String" /> that represents the current SQL.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            return builder.ToString();
        }


        /// <summary>
        ///   Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        ///   A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public object Clone()
        {
            return new Select((SelectBuilder) builder.Clone());
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<returns></returns>
        public Select OrderBy(string fieldName)
        {
            builder.OrderBy.Fields.Add(fieldName);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select OrderBy<T>(Expression<Func<T, object>> expression)
        {
            var stringExpression = FluentHelper.ParseProperty(expression);

            builder.OrderBy.Fields.Add(stringExpression);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<returns></returns>
        public Select OrderByDescending(string fieldName)
        {
            builder.OrderBy.Fields.Add(fieldName, OrderDirection.Desc);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select OrderByDescending<T>(Expression<Func<T, object>> expression)
        {
            var stringExpression = FluentHelper.ParseProperty(expression);

            builder.OrderBy.Fields.Add(stringExpression, OrderDirection.Desc);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "tableSource"></param>
        ///<param name = "joinCondition"></param>
        ///<returns></returns>
        public Select Join(string tableSource, string joinCondition)
        {
            builder.Joins.Add(tableSource, joinCondition);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "tableSource"></param>
        ///<param name = "joinCondition"></param>
        ///<returns></returns>
        public Select LeftJoin(string tableSource, string joinCondition)
        {
            builder.Joins.Add(JoinTypes.Left, tableSource, joinCondition);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "tableSource"></param>
        ///<param name = "joinCondition"></param>
        ///<returns></returns>
        public Select RightJoint(string tableSource, string joinCondition)
        {
            builder.Joins.Add(JoinTypes.Right, tableSource, joinCondition);
            return this;
        }

        /////<summary>
        /////</summary>
        /////<param name="leftExpression"></param>
        /////<param name="rightExpression"></param>
        /////<returns></returns>
        //public Select Join<T, K>(Expression<Func<T, object>> leftExpression, Expression<Func<K, object>> rightExpression) where K : class
        //{
        //    string tableSource = LambdaExpressionParser.GetTableName<K>();
        //    string joinExpression = LambdaExpressionParser.GetTableName<T>() + "." + LambdaExpressionParser.Parse(leftExpression) + " = " + tableSource + "." + LambdaExpressionParser.Parse(rightExpression);
        //    builder.Joins.Add(tableSource, joinExpression);
        //    return this;
        //}

        ///<summary>
        ///</summary>
        ///<param name = "joinType"></param>
        ///<param name = "tableSource"></param>
        ///<param name = "joinCondition"></param>
        ///<returns></returns>
        public Select Join(JoinTypes joinType, string tableSource, Filters joinCondition)
        {
            var joinExpression = joinCondition.ToString();
            builder.Joins.Add(joinType, tableSource, joinExpression);
            return this;
        }


        ///<summary>
        ///</summary>
        ///<param name = "sqlField"></param>
        ///<returns></returns>
        public Select GroupBy(SqlField sqlField)
        {
            builder.GroupBy.Fields.Add(sqlField);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select GroupBy<T>(Expression<Func<T, object>> expression)
        {
            var stringExpression = FluentHelper.ParseProperty(expression);

            builder.GroupBy.Fields.Add(stringExpression);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select GroupBy(string expression)
        {
            builder.GroupBy.Fields.Add(expression);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "tableSource"></param>
        ///<param name = "leftExpression"></param>
        ///<param name = "compareOperator"></param>
        ///<param name = "rightExpression"></param>
        ///<returns></returns>
        public Select Join(string tableSource, object leftExpression, SqlOperator compareOperator, object rightExpression)
        {
            builder.Joins.Add(tableSource, leftExpression, compareOperator, rightExpression);
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "leftExpression"></param>
        ///<param name = "rightExpression"></param>
        ///<returns></returns>
        public Select Join<K1, K2>(Expression<Func<K1, object>> leftExpression, Expression<Func<K2, object>> rightExpression)
            where K1 : class
            where K2 : class
        {
            string tableSource = FluentHelper.GetTableName<K2>();
            SqlField leftField = FluentHelper.GetField(leftExpression);
            SqlField rightField = FluentHelper.GetField(rightExpression);

            builder.Joins.Add(JoinTypes.Inner, tableSource, leftField.ToString(), SqlOperator.Equals, rightField.ToString());

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "leftExpression"></param>
        ///<param name = "rightExpression"></param>
        ///<returns></returns>
        public Select LeftJoin<K1, K2>(Expression<Func<K1, object>> leftExpression, Expression<Func<K2, object>> rightExpression)
            where K1 : class
            where K2 : class
        {
            string tableSource = FluentHelper.GetTableName<K2>();
            SqlField leftField = FluentHelper.GetField(leftExpression);
            SqlField rightField = FluentHelper.GetField(rightExpression);

            builder.Joins.Add(JoinTypes.Left, tableSource, leftField.ToString(), SqlOperator.Equals, rightField.ToString());

            return this;
        }

        ///<summary>
        ///</summary>
        ///<returns></returns>
        public Select Distinct()
        {
            builder.Select.SelectionType = SelectionTypes.Distinct;
            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "expression"></param>
        ///<returns></returns>
        public Select Having(string expression)
        {
            builder.Having.Filters.Add(expression);
            return this;
        }


        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<param name = "fieldValue"></param>
        ///<returns></returns>
        public Select Having<T>(Expression<Func<T, object>> fieldName, object fieldValue)
        {
            var stringExpression = FluentHelper.ParseProperty(fieldName);

            builder.Having.Filters.Add(stringExpression, fieldValue);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<param name = "fieldName"></param>
        ///<param name = "compareOperator"></param>
        ///<param name = "fieldValue"></param>
        ///<returns></returns>
        public Select Having<T>(Expression<Func<T, object>> fieldName, SqlOperator compareOperator, object fieldValue)
        {
            var stringExpression = FluentHelper.ParseProperty(fieldName);

            builder.Having.Filters.Add(stringExpression, compareOperator, fieldValue);

            return this;
        }

        ///<summary>
        ///</summary>
        ///<returns></returns>
        public SelectBuilder ToSelectBuilder()
        {
            return (SelectBuilder) builder.Clone();
        }

    }

    ///<summary>
    ///</summary>
    public interface ISelect
    {
        ///<summary>
        ///</summary>
        ///<returns></returns>
        SelectBuilder ToSelectBuilder();
    }
}